package mmusimulator;

import java.util.Random;

/**
 *
 * @author Michal
 */
public class MMU {

    byte[] nextVirtualAddress;
    long decMainTableShiftAdr;
    long decSecondaryTableShiftAdr;
    long decOffset;
    int dataCounter;    // Simulační náplň - uloží se do nových rámců

    public MMU() {
        init();

    }

    private void init() {
        nextVirtualAddress = new byte[24];
        dataCounter = 0;
    }

    public long getPhysicalAddress(Processor cpu, byte[][] ram, byte[][] swap) {
        long secondaryTableRecordAddress = readSecondaryTableAddressEntry(cpu, ram);
        System.out.print("INFO: Byl načten záznam stránky na pozici: " + secondaryTableRecordAddress + " s hodnotou: ");
        Simulator.printArray(ram[(int) secondaryTableRecordAddress]);

        int validPage = ram[(int) secondaryTableRecordAddress][30];
        // Adresa rámce
        long frameAddress;


        if (validPage == 1) {                                                   // stránka existuje

            long swappedPage = ram[(int) secondaryTableRecordAddress][29];

            if (swappedPage == 1) {

                //INTERRUPT                                                       // potřebujeme načíst stránku/rámec do paměti
                System.out.println("INTERRUPT: Výpadek stránky - stránka je odložená");
                long swappedPageAddress = Simulator.binaryArrayToDec(getBitsFromAdress(ram[(int) secondaryTableRecordAddress], 21, 0));
                frameAddress = getFreeFrameAddress(ram, swap);
                loadFrameFromSwap(swappedPageAddress, frameAddress, ram, swap);
                System.out.println("INFO: Rámec ze swapu na adrese " + swappedPageAddress + " byl uložen do RAM na adresu: " + frameAddress);
                long updatedEntry = createNewRamEntry(frameAddress);
                ram[(int) secondaryTableRecordAddress] = Simulator.decimalToBinaryArray(updatedEntry);
                System.out.print("INFO: Aktualizuju záznam stránky: " + (updatedEntry - Simulator.getBitRange(30) - 5) + " v podtabulce RAM na hodnotu: ");
                Simulator.printArray(ram[(int) secondaryTableRecordAddress]);

            } else {                                                            // stránka čeká v paměti
                frameAddress = Simulator.binaryArrayToDec(getBitsFromAdress(ram[(int) secondaryTableRecordAddress], 7, 0)) * 1024;                                             // Frame
            }
            long physicalAddress = makePhysicalAddr(frameAddress, decOffset);

            return physicalAddress;

        } else {                                                                // výpadek stránky
            System.out.println("INTERRUPT: Výpadek stránky - stránka neexistuje - řízení přebírá OS");
            byte[][] dataFrame = findMissingDataFrame();
            frameAddress = getFreeFrameAddress(ram, swap);
            fetchDataFrameToRAM(dataFrame, ram);
            long newTableEntry = createNewRamEntry(frameAddress);
            ram[(int) secondaryTableRecordAddress] = Simulator.decimalToBinaryArray(newTableEntry);
            System.out.print("INFO: Aktualizuju záznam načtené stránky: " + (newTableEntry - Simulator.getBitRange(30) - 5) + " v podtabulce RAM na hodnotu: ");
            Simulator.printArray(ram[(int) secondaryTableRecordAddress]);

            long physicalAddress = makePhysicalAddr(frameAddress, decOffset);

            return physicalAddress;
        }


    }

    private long readSecondaryTableAddressEntry(Processor cpu, byte[][] ram) {
        byte[] virtualAddress = cpu.getNextVirtualAddress();

        // Ziská adresu posunu z hlavni tabulky
        byte[] mainTableShiftAdr = getBitsFromAdress(virtualAddress, 23, 18);
        System.out.print("INFO: Posun v hlavní tabulce 24b-19b: ");
        Simulator.printArray(mainTableShiftAdr);
        // Ziská adresu posunu z vedlejsi tabulky
        byte[] secondaryTableShiftAdr = getBitsFromAdress(virtualAddress, 17, 12);
        System.out.print("INFO: Posun ve vedlejší tabulce 18b-13b: ");
        Simulator.printArray(secondaryTableShiftAdr);
        // Ziská offset
        byte[] offsetAdr = getBitsFromAdress(virtualAddress, 11, 2);

        //Decimální hodnota PCB
        long decPCB = Simulator.binaryArrayToDec(ram[0]);

        // Decimální hodnota posunu z hlavní  a vedlejší tabulky + počátek tabulky
        decMainTableShiftAdr = Simulator.binaryArrayToDec(mainTableShiftAdr);
        decSecondaryTableShiftAdr = Simulator.binaryArrayToDec(secondaryTableShiftAdr);
        decOffset = Simulator.binaryArrayToDec(offsetAdr);
        System.out.print("INFO: Offset je " + decOffset + ", 12b-3b: ");
        Simulator.printArray(offsetAdr);


        /*
         * System.out.println(decMainTableShiftAdr);
         * System.out.println(decSecondaryTableShiftAdr);
         * System.out.println(decOffset);
         */

        // adresa v hlavní tabulce
        //System.out.println("INFO: Záznam z hlavní tabulky je PCB " + decPCB + ", posun v hlavní tabulce " + decMainTableShiftAdr);
       
        long mainTableRecordAddress = decPCB + decMainTableShiftAdr;
        // adresa ve vedlejší tabulce
        return Simulator.binaryArrayToDec(ram[(int) mainTableRecordAddress]) + decSecondaryTableShiftAdr;

        /*
         * System.out.println(mainTableRecordAddress);
         * System.out.println(secondaryTableRecordAddress);
         */
    }

    private long getFreeFrameAddress(byte[][] ram, byte[][] swap) {

        updateFreeFrameAddress(ram, swap);
        long frameAddr = Simulator.binaryArrayToDec(ram[4161]);
        System.out.print("INFO: Nalezen volný rámec: " + frameAddr + ", ");
        Simulator.printArray(ram[4161]);
        return frameAddr;
    }

    public void updateFreeFrameAddress(byte[][] ram, byte[][] swap) {

        
            long freeFrame = Simulator.binaryArrayToDec(ram[4161]) + Simulator.getBitRange(10);

            if (freeFrame <= 261120 && Simulator.binaryArrayToDec(ram[4162]) == 0) {
                ram[4161] = Simulator.decimalToBinaryArray(freeFrame);

            } else {

                // zapnutí Swapování
                System.out.println("INFO: RAM zaplněna, SWAPUJI");
                ram[4162] = Simulator.decimalToBinaryArray(1);
                Random rand = new Random();
                int frameToDrop = rand.nextInt(251) + 5;
                ram[4161] = Simulator.decimalToBinaryArray(frameToDrop*1024);
                System.out.println("INFO: Odkládám stránku: " + (frameToDrop - 5));

                // Uložení stránky na SWAP
                long addrInSwap = dropFrameToSwap(swap, ram);

                // Aktualizace záznamu na RAM
                int affectedVirtualPage = findAffectedVirtualPage(ram);
                System.out.println("INFO: Nalezen záznam odkládané stránky na adrese: " + (affectedVirtualPage));
                long newPageTableEntry = createNewSwappedEntry(addrInSwap);
                //System.out.println("INFO: Zapisuji následující adresu na : " + newPageTableEntry);
                ram[affectedVirtualPage] = Simulator.decimalToBinaryArray(newPageTableEntry);
                System.out.print("INFO: Aktualizuju záznam odkládané stránky v podtabulce RAM na hodnotu: ");
                Simulator.printArray(ram[affectedVirtualPage]);
            }



        

    }

    public byte[] getBitsFromAdress(byte[] adr, int highestBit, int lowestBit) {

        nextVirtualAddress = adr;
        byte[] tableShiftAddr = new byte[highestBit - lowestBit + 1];
        int shiftAddrLenghtIndex = tableShiftAddr.length - 1;

        //Načtení hodnoty pro posun z hlavní tabulky (Vrchních 6 bitů)

        for (int i = highestBit;
                i >= lowestBit; i--) {
            tableShiftAddr[shiftAddrLenghtIndex] = adr[i];
            shiftAddrLenghtIndex--;
        }

        return tableShiftAddr;

    }

    private long makePhysicalAddr(long frameAddress, long decOffset) {
        return frameAddress + decOffset;
    }

    private int findAffectedVirtualPage(byte[][] ram) {

        long frameToSwap = Simulator.binaryArrayToDec(ram[4161]);

        for (int i = 65; i <= 4160; i++) {
            if (ram[i][29] == 0 && ram[i][30] == 1) {
                byte[] pageTableEntry = getBitsFromAdress(ram[i], 7, 0);
                long frameNumber = Simulator.binaryArrayToDec(pageTableEntry);
                if (frameNumber*1024 == frameToSwap) {
                    return i;
                }
            }

        }
        return 0;

    }

    private long dropFrameToSwap(byte[][] swap, byte[][] ram) {

        long frameToSwap = Simulator.binaryArrayToDec(ram[4161]);

        long freeSwapFrame = Simulator.binaryArrayToDec(ram[4163]);

        for (int i = 0; i < Simulator.getBitRange(10); i++) {
            swap[(int) freeSwapFrame + i] = ram[(int) frameToSwap + i];
        }

        ram[4163] = Simulator.decimalToBinaryArray(freeSwapFrame + Simulator.getBitRange(10));

        return freeSwapFrame;
    }

    private long createNewSwappedEntry(long addrInSwapp) {
        System.out.println("INFO: První volná adresa ve SWAPU: " + addrInSwapp);
        byte[] pageTableEntry = new byte[32];
        pageTableEntry[30] = 1;     // Status bit of validity
        pageTableEntry[29] = 1;     // Status bit of swapp
        long decimalEntry = Simulator.binaryArrayToDec(pageTableEntry);
        return decimalEntry + addrInSwapp;
    }

    private long createNewRamEntry(long frameAddr) {
        byte[] pageTableEntry = new byte[32];
        pageTableEntry[30] = 1;     // Status bit of validity
        pageTableEntry[29] = 0;     // Status bit of swapp
        long decimalEntry = Simulator.binaryArrayToDec(pageTableEntry);
        //Simulator.printArray(Simulator.decimalToBinaryArray(frameAddr));
        //Simulator.printArray(Simulator.decimalToBinaryArray(decimalEntry));
        return decimalEntry + (long) frameAddr / 1024;
    }

    private byte[][] findMissingDataFrame() {

        byte[][] dataFrame = new byte[1024][32];

        for (int i = 0; i < 1024; i++) {
            dataFrame[i] = Simulator.decimalToBinaryArray(dataCounter);
        }
        dataCounter++;

        System.out.println("OS: Data nalezena, předávám simulovaný rámec naplněný hodnotami: " + dataCounter);

        return dataFrame;
    }

    private void fetchDataFrameToRAM(byte[][] dataFrame, byte[][] ram) {

        long freeFrameInRAM = Simulator.binaryArrayToDec(ram[4161]);

        for (int i = 0; i < Simulator.getBitRange(10); i++) {
            ram[(int) freeFrameInRAM + i] = dataFrame[i];
        }

        System.out.println("INFO: Nová stránka načtena z disku do RAM.");

    }

    private void loadFrameFromSwap(long swappedPageAddress, long frameAddress, byte[][] ram, byte[][] swap) {

        for (int i = 0; i < Simulator.getBitRange(10); i++) {
            ram[(int) frameAddress + i] = swap[(int) swappedPageAddress + i];
        }
    }
}
